using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;

namespace LibFetion
{
    public class FetionCommunicationManager
    {
        private readonly Guid guid = Guid.Empty;
        private Socket socket;
        private DealingBuffer<byte> reciveBuffer;

        public FetionCommunicationManager()
        {
            guid = Guid.NewGuid();
            reciveBuffer = new DealingBuffer<byte>();
        }

        public HttpWebRequest CreateFetionRequest(string url, string ssic, string data)
        {
            HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
            request.UserAgent = "IIC2.0/PC 3.2.0540";
            request.ContentType = "application/oct-stream";
            if (!string.IsNullOrEmpty(ssic))
            {
                CookieContainer cookieContainer = new CookieContainer();
                Cookie cookie = new Cookie("ssic", ssic);
                cookie.Path = "/";
                cookie.Domain = request.RequestUri.Host;
                cookieContainer.Add(cookie);
                request.CookieContainer = cookieContainer;
            }
            request.Headers.Add("Pragma", "xz4BBcV" + guid);

            if (!string.IsNullOrEmpty(data))
            {
                request.Method = "POST";
                byte[] postdata = Encoding.UTF8.GetBytes(data);
                request.GetRequestStream().Write(postdata, 0, postdata.Length);
                request.GetRequestStream().Close();
            }
            return request;
        }

        public void ConnectToServer()
        {
            Socket socketToServer = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            IPAddress serverIP = IPAddress.Parse(ConstValue.ServerIP);
            if (serverIP == null) return;
            socketToServer.Connect(serverIP, ConstValue.ServerPort);
            socket = socketToServer;
        }

        public string Send(string message)
        {
            byte[] bytesToSend = Encoding.UTF8.GetBytes(message);
            byte[] rev = new byte[1024];
            int count = 0;
            StringBuilder builder = new StringBuilder();
            socket.Send(bytesToSend);

            while ((count = socket.Receive(rev)) > 0)
            {
                builder.Append(Encoding.UTF8.GetString(rev, 0, count));
                reciveBuffer.AppendData(rev, 0, count);
                if (count < 1024) break;
            }
            return builder.ToString();
        }

        public HttpWebResponse ExecRequest(HttpWebRequest request)
        {
            HttpWebResponse response = (HttpWebResponse)request.GetResponse();
            return response;
        }

        public HttpWebResponse ExecRequest(string url, string ssic, string data)
        {
            HttpWebRequest request = CreateFetionRequest(url, ssic, data);
            HttpWebResponse response = (HttpWebResponse)request.GetResponse();
            return response;
        }
    }

    public class DealingBuffer<T>
    {
        private T[] dataStore;
        private int begin = 0;
        private int end = 0;
        private bool empty = true;
        private int expectingWriteSize = 0;
        private int expectingReadSize = 0;
        private readonly int buffCapacity = 1024 * 1024;
        private AutoResetEvent readLock;
        private AutoResetEvent writeLock;

        public DealingBuffer()
        {
            dataStore = new T[buffCapacity];
            readLock = new AutoResetEvent(false);
            writeLock = new AutoResetEvent(true);
        }

        public DealingBuffer(int buffSize)
        {
            dataStore = new T[buffSize];
            buffCapacity = buffSize;
            readLock = new AutoResetEvent(false);
            writeLock = new AutoResetEvent(true);
        }

        public bool IsFull()
        {
            return (begin == end) && !empty;
        }
        public bool IsFull(int appendSize)
        {
            return (begin == (end + appendSize) % buffCapacity) && !empty;
        }
        public int GetSize()
        {
            if (begin > end) return (end + buffCapacity) - begin;
            return end - begin;
        }


        public void AppendData(T element)
        {
            expectingWriteSize = 1;
            if (IsFull()) writeLock.WaitOne();
            dataStore[end] = element;
            end = (end + 1) % buffCapacity;
            empty = false;
            expectingWriteSize = 0;
            if (GetSize() >= expectingReadSize)
                readLock.Set();
        }

        public void AppendData(T[] elements, int beginIndex, int size)
        {
            expectingWriteSize = size;
            if (IsFull(size - 1)) writeLock.WaitOne();
            if (end + size <= buffCapacity)
                Buffer.BlockCopy(elements, beginIndex, dataStore, end, size);
            else
            {
                int part2 = end + size - buffCapacity;
                int part1 = end + size - part2;
                Buffer.BlockCopy(elements, beginIndex, dataStore, end, part1);
                Buffer.BlockCopy(elements, beginIndex + part1, dataStore, 0, part2);

            }
            end = (end + size) % buffCapacity;
            empty = false;
            expectingWriteSize = 0;
            if (GetSize() >= expectingReadSize)
                readLock.Set();
        }

        public T ReadData()
        {
            expectingReadSize = 1;
            if (empty) readLock.WaitOne();
            T element = dataStore[begin];
            begin = (begin + 1) % buffCapacity;
            empty = (begin == end);
            expectingReadSize = 0;
            if (buffCapacity - GetSize() >= expectingWriteSize)
                writeLock.Set();
            return element;
        }

        public void ReadData(T[] elements, int beginIndex, int size)
        {
            expectingReadSize = size;
            if (GetSize() < size) readLock.WaitOne();
            if (begin + size <= buffCapacity)
                Buffer.BlockCopy(dataStore, begin, elements, beginIndex, size);
            else
            {
                int part2 = begin + size - buffCapacity;
                int part1 = begin + size - part2;
                Buffer.BlockCopy(dataStore, begin, elements, beginIndex, part1);
                Buffer.BlockCopy(dataStore, 0, elements, beginIndex + part1, part2);

            }
            begin = (begin + size) % buffCapacity;
            empty = (begin == end);
            expectingReadSize = 0;
            if (buffCapacity - GetSize() >= expectingWriteSize)
                writeLock.Set();
        }
    }
}